/*
 * Copyright (c) 2010-2011, okazoh_tk. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *  - Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *  - Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *  - Neither the name of the copyright holder nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "alkes/graph/UpdateContext.h"
#include <string.h>

namespace alkes {

UpdateContext::UpdateContext(RenderTarget* target)
: target_(target)
, target_rect_(target->getTargetRect())
, force_dirty_(false)
, target_behavior_(target->getTargetBehavior())
{
    history_[0] = 0;
    history_[1] = 0;

    if (target_behavior_ != AL_BEHAVIOR_DISCARD)
    {
        dirty_rects_.addRect(target_rect_);
    }

    AL_NOTICE("target_behavior=0x%02x", target_behavior_);

    switch (target_behavior_)
    {
    case AL_BEHAVIOR_TRIPLE:
        history_[1] = 1;
        dirty_rects_.addRect(target_rect_, 1);

        // fall through

    case AL_BEHAVIOR_DOUBLE:
        history_[0] = 1;
        dirty_rects_.addRect(target_rect_, 2);
        break;
    default:
        break;
    }

    history_count_ = history_[0] + history_[1];
}

UpdateContext::~UpdateContext()
{
}
/*
IGraphics2DDevice* UpdateContext::getGraphics2DDevice() const
{
    return device_;
}

IGraphics3DDevice* UpdateContext::getGraphics3DDevice() const
{
    return 0;
}*/

void UpdateContext::addOffset(int16_t x, int16_t y)
{
    scissor_.addOffset(x, y);
}

void UpdateContext::minusOffset(int16_t x, int16_t y)
{
    scissor_.minusOffset(x, y);
}

void UpdateContext::pushScissorSize(int16_t w, int16_t h)
{
    scissor_.pushScissorSize(w, h);
}

void UpdateContext::popScissorRect()
{
    scissor_.popScissorRect();
}

const Rect& UpdateContext::getCurrentScissorRect()
{
    return scissor_.getCurrentRect();
}

RenderTarget* UpdateContext::getRenderTarget()
{
    return target_;
}

const Rect& UpdateContext::getTargetRect() const
{
    return target_rect_;
}

bool UpdateContext::relayDirtyFlag(bool child_dirty_flag)
{
    return force_dirty_ || child_dirty_flag;
}

bool UpdateContext::setForceDirty(bool force_dirty)
{
    bool ret = (force_dirty_ != force_dirty);

    if (ret)
        force_dirty_ = force_dirty;

    return ret;
}

bool UpdateContext::isForceDirty() const
{
    return force_dirty_;
}

bool UpdateContext::hasDirtyRect() const
{
    //if (target_behavior_ == AL_BEHAVIOR_DISCARD)
    //    return true;

    return 0 != (dirty_rects_.getCount() - history_count_);
}

void UpdateContext::addDirtyRect(const Rect& rect)
{
    uint16_t guard = history_count_;

    if (target_behavior_ == AL_BEHAVIOR_DISCARD)
        dirty_rects_.addRect(target_rect_, guard);
    else
        dirty_rects_.addRect(rect, guard);
}

void UpdateContext::resetDirtyRect()
{
//    if (target_behavior_ == AL_BEHAVIOR_DISCARD)
//        return ;

    uint16_t shrink_count = 0;
    if (target_behavior_ == AL_BEHAVIOR_DOUBLE)
    {
        shrink_count = history_[0];
        history_[0] = dirty_rects_.getCount() - shrink_count;
    }
    else if (target_behavior_ == AL_BEHAVIOR_TRIPLE)
    {
        shrink_count = history_[0];
        history_[0] = history_[1];
        history_[1] = dirty_rects_.getCount() - (shrink_count + history_[1]);
    }
    else
    {
        shrink_count = dirty_rects_.getCount();
    }

    // update history count
    history_count_ = history_[0] + history_[1];

    dirty_rects_.shrink(shrink_count);
}

void UpdateContext::cancelDirtyRect()
{
    dirty_rects_.cancel(dirty_rects_.getCount() - history_count_);
}

const Rect* UpdateContext::getMergedRects(uint16_t& count)
{
    //if (target_behavior_ == AL_BEHAVIOR_DISCARD)
    //{
    //    count = 1;
    //    return &target_rect_;
    //}

    return dirty_rects_.getMergedRects(count);
}


RectStack::RectStack()
: merged_(false)
, max_(0)
, count_(0)
, rects_(0)
, merged_max_(0)
, merged_count_(0)
, merged_rects_(0)
{
}

RectStack::~RectStack()
{
    if (rects_)
    {
        alkes_free(rects_);
    }

    if (merged_rects_)
    {
        alkes_free(merged_rects_);
    }
}

uint16_t RectStack::getCount() const
{
    return count_;
}

bool RectStack::hasDirtyRect() const
{
    return count_ != 0;
}

void RectStack::addRect(const Rect& rect, uint16_t guard)
{
    if (rect.isNull())
        return ;

    if (count_)
    {
        // try to merge rect.
        int16_t i = count_ - 1;
        while (0 <= i && guard <= i)
        {
            if (rect.contains(rects_[i]))
                ; // next rect
            else if (rects_[i].contains(rect))
                return ; // do nothing.
            else
                break;
            --i;
        }
        count_ = i + 1;
    }

    if (count_ < max_ || expand())
    {
        rects_[count_] = rect;
        ++count_;
    }

    merged_ = false;
}

const Rect* RectStack::getMergedRects(uint16_t& count)
{
    if (0 == count_)
        return 0;

    if (merged_)
    {
        count = merged_count_;
        return merged_rects_;
    }

    if (merged_max_ < count_)
    {
        if (merged_rects_)
        {
            alkes_free(merged_rects_);
        }
        merged_rects_ = (Rect*)alkes_malloc(sizeof(Rect) * max_);
        merged_max_ = max_;
    }

    if (!merged_rects_)
    {
        AL_ASSERT(false);
        return 0;
    }

    // merge rect
    uint16_t i;
    merged_rects_[0] = rects_[0];
    for (i = 1, merged_count_ = 0; i < count_; ++i)
    {
        if (rects_[i].isOverlap(merged_rects_[merged_count_]))
        {
            merged_rects_[merged_count_].joinWith(rects_[i]);
        }
        else
        {
            ++merged_count_;
            merged_rects_[merged_count_] = rects_[i];
        }
    }

    ++merged_count_;

    merged_ = true;

    //AL_DBG("count=%d", merged_count_);
    //for (i = 0; i < merged_count_; ++i)
    //{
    //    AL_DBG("rect=(%3d,%3d,%3d,%3d)",
    //        merged_rects_[i].x, merged_rects_[i].y,
    //        merged_rects_[i].width, merged_rects_[i].height);
    //}

    count = merged_count_;
    return merged_rects_;
}

bool RectStack::expand()
{
    int16_t new_count;
    if (0 == max_)
        new_count = 4;
    else
        new_count = max_ * 2;

    Rect* new_rect = (Rect*)alkes_malloc(sizeof(Rect) * new_count);

    if (!new_rect)
    {
        return false;
    }

    if (rects_ && count_)
    {
        memcpy(new_rect, rects_, sizeof(Rect) * count_);
    }

    if (rects_)
    {
        alkes_free(rects_);
    }

    rects_ = new_rect;
    max_ = new_count;

    return true;
}

void RectStack::shrink(uint16_t count)
{
    if (0 == count)
        return ;

    if (count_ <= count)
    {
        count_ = 0;
        return ;
    }

    count_ = count_ - count;

    memmove(rects_, rects_ + count, sizeof(Rect) * count_);
}

void RectStack::cancel(uint16_t count)
{
    if (count_ < count)
        count_ = 0;
    else
        count_ = count_ - count;
}

}
